package net.vsame.url2sql.helper;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import net.vsame.url2sql.url.impl.UrlConfig;
import net.vsame.url2sql.url.impl.UrlMapping;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public abstract class CacheHelper {
	
	public static enum EnumCacheType {
		/** 按用户进行缓存 */
		USER, 
		/** 全局缓存 */
		GLOBAL, 
		/** 不缓缓存 */
		NONE
	}
	
	/**
	 * 获取 SessionId，用以区分不同用户。如果允按用户分离，请返回用户Id，反之，使用SessionId
	 * @return
	 */
	public abstract String getSessionId();
	
	/**
	 * 保存缓存
	 * @param cacheKey 缓存名称
	 * @param cacheJson 缓存值
	 * @param time 缓存时长, NULL 请使用默认缓存
	 */
	public abstract void saveCache(String cacheKey, String cacheJson, Long time);
	
	/** 读取缓存 */
	public abstract String readCache(String cacheKey);
	
	/** 删除缓存 
	 * @param cleanPaths */
	public abstract void delCacheBefore(List<String> cleanPaths);
	
	
	public String getCacheName(){
		return "redis";
	}
	
	/**
	 * 取得清除缓存 KEY
	 * @param urlConfig 
	 * @param cacheConfig true 表示按用户进行分离
	 * @return
	 */
	public String getCleanCacheKey(UrlConfig urlConfig){
		EnumCacheType type = urlConfig.getCacheType();
		if(type == EnumCacheType.NONE) {
			return null;
		}
		
		String userStr = "?" + type.toString();
		if(type == EnumCacheType.USER) {
			userStr = userStr + "=" + getSessionId();
		}
		return urlConfig.getUrl().substring(1) + userStr;
	}
	
	/**
	 * 取得缓存 KEY
	 * @return
	 */
	public String getCacheKey(){
		Url2SqlContext context = WebHelper.getContext();
		String cleanKey = getCleanCacheKey(context.getUrlConfig());
		if(cleanKey == null) {
			return null;
		}
		return cleanKey + "&" + dictSort(context.getParams());
	}
	
	/**
	 * 所有参数字典排序
	 * @param params
	 * @param secretKey
	 * @return
	 */
	private static String dictSort(Map<String, String[]> params) {
		// 排除 signature
		ArrayList<String> list = new ArrayList<String>(params.size()*3/2);//尽量避免重新分配内存空间
		for(Map.Entry<String, String[]> e : params.entrySet()){
			if(e.getKey().startsWith("application.")) {
				continue;
			}
			if(e.getKey().startsWith("actionScope.")) {
				continue;
			}
			for(String str : e.getValue()) {
				list.add(e.getKey() + "=" + encodeUrl(str));
			}
		}
		
		//字典排序
		String[] array = list.toArray(new String[] {});
		Arrays.sort(array, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.compareTo(o2);
			}
		});
		
		//生成原始字符串
		StringBuffer sb = new StringBuffer();
		for(String str : array) {
			sb.append(str).append("&");
		}
		if(sb.length() > 0) {
			sb.deleteCharAt(sb.length()-1);
		}
		return sb.toString();
	}
	
    /** 
     * URL编码 (符合FRC1738规范)
     * @param input 待编码的字符串
     * @return 编码后的字符串
     * @throws OpensnsException 不支持指定编码时抛出异常。
     */
    private static String encodeUrl(String input)    {
        try {
            return URLEncoder.encode(input, "UTF-8").replace("+", "%20").replace("*", "%2A");
        }
        catch(UnsupportedEncodingException e) {
        	throw new RuntimeException(e);
        }
    }
	
	/**
	 * 设置缓存
	 * @param key 
	 */
	public void setCache(final String cacheKey){
		Url2SqlContext context = WebHelper.getContext();
		UrlConfig urlConfig = context.getUrlConfig();
		EnumCacheType type = urlConfig.getCacheType();
		
		if(type == EnumCacheType.NONE) {//无缓存
			return ;
		}
		
		Map<String, Object> datas = WebHelper.getContext().getDatas();
		final Map<String, Object> map = new HashMap<String, Object>(datas.size());
		map.putAll(datas);
		map.put("$cacheTime", System.currentTimeMillis());
		
		String json = JSON.toJSONString(map, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.DisableCircularReferenceDetect);
		this.saveCache(cacheKey, json, urlConfig.getCacheTime());		
	}
	

	/**
	 * 获取缓存
	 * @param key
	 * @return
	 */
	public JSONObject getCache(final String cacheKey){
		String json = this.readCache(cacheKey);
		if(json == null) {
			return null;
		}
		return JSON.parseObject(json);
	}
	
	/**
	 * 清除缓存
	 * 将来清除缓存，是根据路径，计算出KEY，根据其前缀，删除所有匹配项
	 */
	public void cleanCache(){
		Url2SqlContext context = WebHelper.getContext();
		UrlConfig urlConfig = context.getUrlConfig();
		
		List<String> list = urlConfig.getCleanPathCaches();
		if(list==null || list.size()==0) {
			return ;
		}
		
		//获取所有匹配的 Url
		UrlConfig[] matchesConfig = matchesUrlConfig(urlConfig.getUrl(), list);
		if(matchesConfig.length == 0) {
			return ;
		}
		
		//计算出找到 Action 的 KEY
		List<String> cleanPaths = new ArrayList<String>();
		for(UrlConfig uc : matchesConfig) {
			cleanPaths.add(this.getCleanCacheKey(uc));
		}
		
		//传递并删除之
		this.delCacheBefore(cleanPaths);
		
	}

	private static ConcurrentHashMap<String, UrlConfig[]> cleanUrlConfigMap = new ConcurrentHashMap<String, UrlConfig[]>();
	private UrlConfig[] matchesUrlConfig(String path, List<String> list) {
		UrlConfig[] array = cleanUrlConfigMap.get(path);
		if(array != null) {
			return array;
		}
		
		Map<String, UrlConfig> map = UrlMapping.getMapping().getUrlSqlMap();
		List<UrlConfig> retList = new ArrayList<UrlConfig>();
		for(String regex : list) {
			Pattern pattern = Pattern.compile(regex);
			for(Entry<String, UrlConfig> e : map.entrySet()) {
				UrlConfig uc = e.getValue();
				if(uc.getCacheType() == EnumCacheType.NONE) {
					continue;
				}
				if(pattern.matcher(uc.getUrl().substring(1)).matches()) {//匹配，需要清除
					retList.add(uc);
				}
			}
		}
		
		array = retList.toArray(new UrlConfig[]{});//使用数组，节约空间
		cleanUrlConfigMap.put(path, array);
		return array;
	}
	
}
